using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
using System.Windows.Forms;
using System.Windows.Media.Imaging;
using Pasta.Medio.Abstract;
using Pasta.Medio.Configuration;
using Pasta.Medio.Domain;
using Pasta.Medio.Models;

namespace Pasta.Medio.Concrete
{
	public class MediaScanner : IMediaScanner
	{
		private const int ProgressInterval = 1;

		private BackgroundWorker _worker;
		private Stopwatch _watch = new Stopwatch();
		private ScannerModel _model = null;			// trhead cache of model
		private int _runner;							// file loop counter

		// the following properties should be injected
		public AppSettings Settings { get; set; }
		public RecordingsRepository Repository { get; set; }

		// Worker Task and State
		public Enums.WorkerState State { get; private set; }
		public long ElapsedTime { get { return _watch.ElapsedMilliseconds; } }
		public Results Results { get; private set; }


		// callback methods
		public ProgressChangedEventHandler ProgressChanged { get; set; }

		public MediaScanner(ScannerModel model)
		{
			_model = model;

			// inject these
			Settings = Program.Settings;
			Repository = Program.Recordings;

			State = Enums.WorkerState.Waiting;
			Results = new Results();

			Initialize();
		}

		private void Initialize()
		{
			// initialize background worker
			_worker = new BackgroundWorker();
			_worker.WorkerReportsProgress = true;
			_worker.ProgressChanged += worker_ProgressChanged;
			_worker.WorkerSupportsCancellation = true;
			_worker.RunWorkerCompleted += worker_RunWorkerCompleted;
			_worker.DoWork += worker_DoWork;
		}

		public bool Start()
		{
			if (!Directory.Exists(_model.Path))
			{
				Results.AddError("Folder {0} does not exist", _model.Path);
				return false;
			}

			if (_worker.IsBusy)
			{
				return false;
			}

			ResetModel();
			_worker.RunWorkerAsync();
			return true;
		}

		private void worker_DoWork(object sender, DoWorkEventArgs e)
		{
			_watch.Restart();
			ChangeState(Enums.WorkerState.Scanning);
			CountFiles(_model.Path, e);
			if (!e.Cancel)
			{
				ChangeState(Enums.WorkerState.Analyzing);
				_watch.Restart();
				ListRecordings(_model.Path, e);
			}
			if( !e.Cancel )
			{
				ChangeState(Enums.WorkerState.Complete);
			}
		}

		private void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
		{
			_watch.Stop();
			if (e.Error != null)
			{
				Results.AddError(e.Error);
				ChangeState(Enums.WorkerState.Error);
			}
			else if (e.Cancelled)
			{
				ChangeState(Enums.WorkerState.Cancelled);
			}
			else // thread complete
			{
				ChangeState(Enums.WorkerState.Complete);
			}
		}

		private void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
		{
			if (ProgressChanged != null)
			{
				ProgressChanged(sender, e);
			}
		}

		public void CountFiles(string path, DoWorkEventArgs e)
		{
			if (_worker.CancellationPending)
			{
				e.Cancel = true;
				return;
			}
			try
			{
				var files = Settings.MediaTypes.SelectMany( ext2 => Directory.GetFiles(path, ext2));
				foreach( var file in files )
				{
					FileInfo fi = new FileInfo(file);
					_model.TotalSize += fi.Length;
				}
				int n = files.Count();
				_model.NumFolders++;
				if (n > 0)
				{
					_model.CurrentFolder = path.Substring(_model.Path.Length);
					_model.NumFiles += n;
					int numImages = files.Sum(file => IsImage(file) ? 1 : 0);
					_model.ImageCount += numImages;
					_model.VideoCount += n - numImages;
					_worker.ReportProgress(0, _model);
				}
			}
			catch { }
			try
			{
				foreach (string folder in Directory.GetDirectories(path))
				{
					CountFiles(folder, e);
				}
			}
			catch { }
		}

		private void ChangeState(Enums.WorkerState state)
		{
			State = state;
			if (_worker.IsBusy)
			{
				_worker.ReportProgress(-1, _model);
			}
			else if (ProgressChanged != null)
			{
				ProgressChanged(this, new ProgressChangedEventArgs(-1, _model));
			}

		}

		private void ResetModel()
		{
			State = Enums.WorkerState.Waiting;
			Results.Reset();
			_model.Reset();
		}

		private void ListRecordings(string path, DoWorkEventArgs e)
		{
			_runner = 0;
			Settings.MediaTypes.ForEach(ext => ListRecordings(path, ext, e));
		}

		public void ListRecordings(string path, string ext, DoWorkEventArgs e)
		{
			try
			{
				foreach (var file in Directory.EnumerateFiles(path, ext))
				{
					if (_worker.CancellationPending)
					{
						e.Cancel = true;
						return;
					}
					FileInfo fi = new FileInfo(file);
					var mv = new MediaView();
					mv.DisplayId = ++_runner;
					mv.Path = file;
					mv.Type = IsImage(fi) ? Enums.Type.Image : Enums.Type.Video;
					bool success = (mv.Type == Enums.Type.Image) ? GetImageInfo(fi, mv) : GetVideoInfo(fi, mv);
					//media.Camera = _drive.VolumeLabel;
					mv.TargetName = CreateTargetName(fi);
					mv.Size = fi.Length;
					_model.RunningSize += fi.Length;
					mv.Sha1 = Util.ComputeSHA1(mv.Path);

					Random r = new Random();
					bool inCatalog = r.Next(2) == 1;
					mv.InCatalog = inCatalog;
					//mv.InCatalog = Program.Context.Recordings.Any(r => r.Sha1 == mv.Sha1 && r.Size == mv.Size);
					if (!mv.InCatalog)
					{
						if (mv.Type == Enums.Type.Image)
							_model.NewImages++;
						else
							_model.NewVideos++;
					}
					_model.Media.Add(mv);

					// progress
					if (_runner % ProgressInterval == 0)
					{
						_model.LastUpdateIndex = _runner;
						_model.CurrentFolder = path.Substring(_model.Path.Length);
						_model.CurrentFile = Path.GetFileName(file);
						_worker.ReportProgress(100 * _runner / _model.NumFiles, _model);
					}
				}
			}
			catch { }

			try
			{
				foreach (string folder in Directory.GetDirectories(path))
				{
					ListRecordings(folder, ext, e);
				}
			}
			catch { }
		}

		private bool GetImageInfo(FileInfo fi, MediaView media)
		{
			bool success = false;
			if (fi != null && IsImage(fi))
			{
				_model.ImageCount++;
				media.Type = Enums.Type.Image;
				try
				{
					using (FileStream fs = new FileStream(fi.FullName, FileMode.Open, FileAccess.Read, FileShare.Read))
					{
						BitmapSource img = BitmapFrame.Create(fs);
						BitmapMetadata md = (BitmapMetadata)img.Metadata;
						media.RecordedOn = ToDate(md.DateTaken);
						media.ModifiedOn = FileDate(fi);

						string manufacturer = String.Empty;
						if (!String.IsNullOrEmpty(md.CameraManufacturer))
						{
							manufacturer = md.CameraManufacturer + " ";
						}
						if (!String.IsNullOrEmpty(md.CameraModel))
						{
							string[] parts = md.CameraModel.Split(new char[] { ',' });
							media.Camera = manufacturer + parts[parts.Length - 1].Trim();
						}

						media.Size = fi.Length;
						success = true;
					}
				}
				catch { }
			}
			return success;
		}

		private DateTime? ToDate(string ts)
		{
			DateTime? dt = null;
			DateTime val;
			if(DateTime.TryParse(ts, out val))
			{
				dt = val;
			}
			return dt;
		}

		public bool GetVideoInfo(FileInfo fi, MediaView media)
		{
			bool success = false;
			if (fi != null && IsVideo(fi))
			{
				_model.VideoCount++;
				media.Type = Enums.Type.Video;
				try
				{
					using (FileStream fs = new FileStream(fi.FullName, FileMode.Open, FileAccess.Read, FileShare.Read))
					{
						media.RecordedOn = FileDate(fi);
						media.Size = fi.Length;
						success = true;
					}
				}
				catch { }
			}
			return success;
		}

		public DateTime FileDate(FileInfo fi)
		{
			DateTime dt = fi.CreationTime;
			if (fi.LastAccessTime < dt) dt = fi.LastAccessTime;
			if (fi.LastWriteTime < dt) dt = fi.LastWriteTime;
			return dt;
		}

		public bool IsImage(string file)
		{
			return Settings.ImageTypesString.Contains(Path.GetExtension(file).Substring(1).ToLower());
		}

		public bool IsImage(FileInfo fi)
		{
			return fi != null && Settings.ImageTypesString.Contains(fi.Extension.Substring(1).ToLower());
		}

		public bool IsVideo(FileInfo fi)
		{
			return fi != null && Settings.VideoTypesString.Contains(fi.Extension.Substring(1).ToLower());
		}

		public bool Cancel()
		{
			bool stopped = false;
			if (_worker.IsBusy)
			{
				_worker.CancelAsync();
				stopped = true;
			}
			return stopped;
		}

		private string CreateTargetName(FileInfo fi)
		{
			string name = fi.LastWriteTime.ToString(Settings.RenamePattern);
			int n = _model.Media.Count(m => m.RecordedOn == fi.CreationTime);
			if (n > 0)
			{
				name += String.Format(Settings.DuplicatePattern, n + 1);
			}
			name += fi.Extension;
			return name;
		}
	}

	public class ScannerModel
	{
		public ScannerModel(string path)
		{
			Path = path;
			Media = new SortableBindingList<MediaView>();
		}

		public void Reset()
		{
			Media.Clear();
			NumFolders = NumFiles = 0;
			ImageCount = VideoCount = 0;
			NewImages = NewVideos = 0;
			TotalSize = RunningSize = 0;
		}

		public string Path { get; set; }
		public SortableBindingList<MediaView> Media { get; private set; }
		public int ImageCount = 0;
		public int NewImages = 0;
		public int VideoCount = 0;
		public int NewVideos = 0;
		public int NumFiles = 0;
		public int NumFolders = 0;
		public long TotalSize = 0;
		public long RunningSize = 0;

		public int FirstUpdateIndex = 0;
		public int LastUpdateIndex = 0;
		public string CurrentFile { get; set; }
		public string CurrentFolder { get; set; }
	}

	/*
		DriveInfo[] drives = DriveInfo.GetDrives();
		foreach (var drive in drives.Where(d => d.IsReady))
		{
			bt.SharedProps.Caption = drive.Name;
		}

		private DriveInfo GetDriveInfo(char name)
		{
			DriveInfo[] drives = DriveInfo.GetDrives();
			return drives.Where(d => d.Name[0] == name).FirstOrDefault();
		} */
}
